<?php
/**
 * Classes utilitárias para a aplicação
 * @author		João Batista Neto
 * @package		rpo
 * @subpackage	util
 */
namespace rpo\util;

use \ArrayObject;
use rpo\base\Object;
use rpo\base\exception\InvalidArgumentException;
use rpo\base\exception\CastException;
use rpo\util\Collection;

/**
 * Essa classe é o esqueleto de uma implementação da interface Collection.
 * @abstract
 * @author		João Batista Neto
 * @package		rpo
 * @subpackage	util
 * @category	list
 */
abstract class AbstractCollection extends Object implements Collection {
	/**
	 * Armazenamento dos dados
	 * @access private
	 * @var ArrayObject
	 */
	protected $storage;

	/**
	 * Tipo de dado da coleção
	 * @access private
	 * @var string
	 */
	protected $type;

	/**
	 * Constroi uma nova coleção
	 * @param mixed $elements Os elementos iniciais da coleção<p>
	 * $elements pode ser uma matriz, Collection ou um Iterator</p>
	 * @throws LogicException Se algum item da lista inicial de elementos não for um Object
	 * @throws CastException Se $elements não for um tipo válido
	 */
	public function __construct( $elements = null ){
		parent::__construct();

		$this->clear();

		if ( !is_null( $elements ) ){
			if ( is_array( $elements ) || ( $elements instanceOf Collection ) || ( $elements instanceOf Iterator ) || ( $elements instanceOf IteratorAggregate ) ){
				foreach ( $elements as $e ){
					if ( $e instanceOf Object ){
						$this->add( $e );
					} else {
						throw new InvalidArgumentException( 'Apenas objetos podem ser armazenados' );
					}
				}
			} else {
				throw new CastException( sprintf( '$emements precisa ser uma matriz, Collection ou Iterator, %s foi dado.' , gettype( $elements ) ) );
			}
		}
	}

	/**
	 * Recupera a representação da coleção como string
	 * @return string
	 */
	public function __toString(){
		return sprintf( '%s[%s]' , parent::__toString() , implode( ',' , $this->toArray() ) );
	}

	/**
	 * Verifica se o objeto é do tipo da lista
	 * @param Object $o
	 * @param boolean $acceptFirst
	 * @return boolean
	 */
	protected function typeTest( $o , $acceptFirst = true ){
		$ret = false;

		if ( $o instanceOf Object ){
			if ( $this->type == null ){
				$ret = true;

				if ( $acceptFirst ){
					$this->type = $o->getClass()->getName();
				}
			} elseif ( $o->getClass()->getName() == $this->type ){
				$ret = true;
			} else {
				throw new CastException( sprintf( '%s aceita apenas %s.' , $this->getClass()->getName() , $this->type ) );
			}
		} else {
			throw new CastException( sprintf( '%s aceita apenas %s, %s foi dado.' , $this->getClass()->getName() , $this->type , gettype( $o ) ) );
		}

		return $ret;
	}

	/**
	 * Adiciona um novo objeto à coleção
	 * @param Object $o
	 * @return boolean
	 * @throws CastException Se o objeto for de um tipo diferente da lista
	 */
	public function add( Object $o ){
		if ( $this->typeTest( $o ) ){
			$this->storage->append( $o );
		}

		return true;
	}

	/**
	 * Adiciona todos os objetos de uma outra coleção a esta coleção
	 * @param Collection $c
	 * @return boolean
	 */
	public function addAll( Collection $c ){
		foreach ( $c->getIterator() as $o ){
			$this->add( $o );
		}

		return true;
	}

	/**
	 * Limpa a coleção deixando-a sem nenhum elemento
	 * @return boolean
	 */
	public function clear(){
		$this->storage = new ArrayObject();

		return true;
	}

	/**
	 * Recupera o total de elementos da coleção
	 * @return integer
	 */
	public function count(){
		return $this->storage->count();
	}

	/**
	 * Verifica se a coleção contém um determinado elemento
	 * @param Object $o O elemento que será verificado se está contido na coleção
	 * @return boolean
	 */
	public function contains( Object $o ){
		foreach ( $this->getIterator() as $item ){
			if ( $item->equals( $o ) ){
				return true;
			}
		}

		return false;
	}

	/**
	 * Verifica se a coleção possui todos os elementos de outra coleção
	 * @param Collection $c A coleção que possui os elementos que será verificados
	 * @return boolean
	 */
	public function containsAll( Collection $c ){
		foreach ( $c->getIterator() as $o ){
			if ( !$this->contains( $o ) ){
				return false;
			}
		}

		return true;
	}

	/**
	 * Verifica se a coleção está vazia
	 * @return boolean
	 * @see AbstractCollection::count(), AbstractCollection::clear()
	 */
	public function isEmpty(){
		return $this->count() == 0;
	}

	/**
	 * Recupera um Iterator para a coleção
	 * @return Iterator
	 */
	public function getIterator(){
		return $this->storage->getIterator();
	}

	/**
	 * Remove um elemento da coleção
	 * @param Object $o O objeto que se deseja remover
	 * @return boolean
	 */
	public function remove( Object $o ){
		$ret = false;

		foreach ( $this->getIterator() as $key => $e ){
			if ( $o->equals( $e ) ){
				$this->storage->offsetUnset( $key );
				$ret = true;
			}
		}

		return $ret;
	}

	/**
	 * Remove da coleção todos os elementos contidos em outra coleção
	 * @param Collection $c A coleção que contém os elementos que se deseja remover
	 * @return boolean TRUE Se todos os elementos contidos na coleção especificada tiverem sido removidos
	 */
	public function removeAll( Collection $c ){
		foreach ( $c->getIterator() as $e ){
			if ( !$this->remove( $e ) ){
				return false;
			}
		}

		return true;
	}

	/**
	 * Mantém apenas os elementos contidos na outra coleção
	 * @param Collection $c A coleção que contém os elementos que se deseja manter
	 * @return boolean
	 */
	public function retainAll( Collection $c ){
		foreach ( $this->getIterator as $e ){
			if ( !$c->contains( $e ) ){
				if ( !$this->remove( $e ) ){
					return false;
				}
			}
		}

		return true;
	}

	/**
	 * Recupera uma matriz com todos os elementos da coleção
	 * @return array
	 */
	public function toArray(){
		return $this->getIterator()->getArrayCopy();
	}
}